package com.shockweb.configcenter;

import java.io.File;
import java.io.IOException;
import java.nio.channels.Selector;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.MultithreadEventLoopGroup;
import io.netty.channel.ServerChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;

import com.shockweb.common.sitelocal.LANAddress;
import com.shockweb.common.utils.FileTools;
import com.shockweb.common.utils.PropertyFile;
import com.shockweb.client.impl.ConfigCenterClient;
import com.shockweb.configcenter.config.ConfigCenterConfig;
import com.shockweb.configcenter.data.CenterRoot;
import com.shockweb.common.log.LogManager;
import com.shockweb.service.ServiceServer;
import com.shockweb.service.ServiceServerException;

/**
 * 配置中心
 * 
 * @author 彭明华
 * 2018年3月20日 创建
 */
public class ConfigCenter implements Runnable{

    /**
     * 配置中心的配置
     */
	private ConfigCenterConfig config = null;

    
	/**
	 * 配置中心实例
	 */
    private static ConfigCenter server = new ConfigCenter();
    
    /**
     * {@link Bootstrap} sub-class which allows easy bootstrap of {@link ServerChannel}
     *
     */
    private volatile ServerBootstrap b;
    
    /**
     * 获取配置中心的配置
     * @return
     */
	public ConfigCenterConfig getConfig() {
		return config;
	}
	
	/**
	 * 默认的启动方法
	 * 指定配置中心的路径
	 * @param args
	 * @throws CenterServerException
	 */
	public static void main(String[] args) throws CenterServerException{
		if(args!=null && args.length>0){
			startCluster(args[0]);
		}else{
			startCluster(FileTools.getClassPath());
		}
	}
    
	/**
	 * 启动配置中心的方法,带配置文件路径的集群方式启动
	 * @param filePath
	 * @throws IOException
	 */
    public static void startCluster(String filePath) throws CenterServerException {
    	try{
    		if(filePath.startsWith("classpath:")){
    			ClassLoader classLoader = null;
    			if(Thread.currentThread()!=null){
    				classLoader = Thread.currentThread().getContextClassLoader();
    			}else{
    				classLoader = ServiceServer.class.getClassLoader();
    			}
    			filePath = classLoader.getResource(filePath.substring("classpath:".length())).getPath();
    		}
	    	String file = null;
	    	if(new File(filePath).isFile()){
	    		file = filePath;
	    	}else if(new File(filePath).isDirectory()){
	    		file = FileTools.getFullPathFileName(filePath,"config.properties");
	    	}else{
	    		throw new ServiceServerException("配置文件路径" + filePath + "非法");
	    	}
	    	server = new ConfigCenter();
	    	server.config = new ConfigCenterConfig();
	    	Map<String,String> data = PropertyFile.read(file);
	    	if(data==null){
	    		throw new CenterServerException("配置文件" + file + "内容为空");
	    	}
	    	if(data.get("configCenter.hostUrl")!=null){
	    		server.config.setHostUrl(data.get("configCenter.hostUrl").replaceAll(" ", ""));
	    	}
	    	if(data.get("configCenter.mirrorUrl")!=null){
	    		server.config.setMirrorUrl(data.get("configCenter.mirrorUrl").replaceAll(" ", ""));
	    	}
	    	if(data.get("configCenter.secretKey")!=null){
	    		server.config.setSecretKey(data.get("configCenter.secretKey").replaceAll(" ", ""));
	    	}
	    	if(data.get("configCenter.clientSleepTime")!=null){
	    		server.config.setClientSleepTime(new Integer(data.get("configCenter.clientSleepTime").replaceAll(" ", "")));
	    	}
	    	if(data.get("configCenter.clientConnectTimeOut")!=null){
	    		server.config.setClientConnectTimeOut(new Integer(data.get("configCenter.clientConnectTimeOut").replaceAll(" ", "")));
	    	}
	    	if(data.get("configCenter.clientSleepTime")!=null){
	    		server.config.setClientSleepTime(new Integer(data.get("configCenter.clientSleepTime").replaceAll(" ", "")));
	    	}
	    	if(data.get("configCenter.clientIdleStateTime")!=null){
	    		server.config.setClientIdleStateTime(new Integer(data.get("configCenter.clientIdleStateTime").replaceAll(" ", "")));
	    	}
	    	if(data.get("configCenter.timeInterval")!=null){
	    		server.config.setTimeInterval(new Long(data.get("configCenter.timeInterval").replaceAll(" ", "")));
	    	}
	    	Iterator<Entry<String,String>> its = data.entrySet().iterator();
	    	while(its.hasNext()){
	    		Entry<String,String> entry = its.next();
	    		String key = entry.getKey();
	    		String group = "default";
	    		String name = key;
	    		String value = entry.getValue();
	    		if(key.indexOf(".")>0){
	    			group = key.substring(0, key.indexOf("."));
	    			name = key.substring(key.indexOf(".")+1);
	    		}
	    		if(group!=null && name!=null && value!=null && !group.equals("configCenter")){
	    			CenterRoot.getCenterRoot().putConfig(group, name, value);
	    		}
	    	}
			if(server.config.getMirrorUrl()!=null){
            	ConfigCenterClient client = null;
            	try{
            		
            		client = new ConfigCenterClient(server.config.getClientTimeOut(),server.config.getClientConnectTimeOut(),
            				server.config.getClientSleepTime(),server.config.getClientIdleStateTime(),server.config.getSecretKey());
            		client.connect(server.config.getMirrorUrl());
    				client.syncConfig(CenterRoot.getCenterRoot().getConfigs());
            	}finally{
            		if(client!=null){
            			client.close();
            		}
            	}
    		}
	    	server.startUp();
    	}catch(Exception e){
    		throw new CenterServerException("启动服务",e);
    	}
    }
    
	/**
	 * 镜像方式启动配置中心方法，带镜像的集群方式启动
	 * @param hostUrl 当前配置中心url
	 * @param configCenterUrls 其他配置中心url，多个配置中心用逗号分隔
	 */
    public static void startCluster(String hostUrl,String mirrorUrl){
    	server.config = new ConfigCenterConfig();
    	if(hostUrl!=null){
    		server.config.setHostUrl(hostUrl.replaceAll(" ", ""));
    	}
    	if(mirrorUrl!=null){
    		server.config.setMirrorUrl(mirrorUrl.replaceAll(" ", ""));
    	}
    	server.startUp();
    }
    
	/**
	 * 集群方式启动配置中心方法，完整的配置参数
	 * @param config
	 */
    public static void startCluster(ConfigCenterConfig config){
    	server.config = config;
    	server.startUp();
    }
    
	/**
	 * 启动配置中心方法,默认网卡地址和2000端口
	 */
    public static void start() {
    	server.config = new ConfigCenterConfig();
    	server.config.setHostUrl(LANAddress.getLocalHostLANAddress().getHostAddress() + ":2000");
    	server.startUp();
    }
    
    /**
     * 公用的启动服务方法
     */
    private synchronized void startUp(){
    	LogManager.infoLog(this.getClass(),"server=" + config.getHostUrl());
    	Thread thread = new Thread(this);
    	thread.start();
    }
    
    /**
     * 启动配置中心方法，完整的配置参数参数
     * @param config
     */
    public static void start(ConfigCenterConfig config) {
    	server.config = config;
    	Thread thread = new Thread(server);
    	thread.start();
    }
    
    /**
     * 启动配置中心方法，只定地址端口hostUrl
     * @param hostUrl
     */
    public static void start(String hostUrl) {
    	server.config = new ConfigCenterConfig();
    	if(hostUrl!=null){
    		server.config.setHostUrl(hostUrl.replace(" ", ""));
    	}
    	Thread thread = new Thread(server);
    	thread.start();
    }
    
	/**
	 * {@link MultithreadEventLoopGroup} implementations which is used for NIO {@link Selector} based {@link Channel}s.
	 */
    private volatile EventLoopGroup workerGroup;
    
    /**
     * {@link EventLoopGroup}
     */
    EventLoopGroup bossGroup;
    
    /**
     * 异步启动配置中心方法
     */
    public void run() {
        // 一个线程组用于处理服务器接收客户端的连接
        // 另一个线程组用于处理SocketChannel的网络读写，用于网络事件的处理
        // EventLoopGroup就是用来管理调度他们的，注册Channel，管理他们的生命周期
        bossGroup = new NioEventLoopGroup();
        workerGroup = new NioEventLoopGroup();
        server = this;
        try {
            // ServerBootstrap用于启动ServerChannel的，是服务端的工具类，Bootstrap是用于启动Channel，
            b = new ServerBootstrap();
            final EventLoopGroup workerGroup = this.workerGroup;
            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                            ch.pipeline().addLast("frameEncoder", new LengthFieldPrepender(4));
                            //当有操作操作超出指定空闲秒数时，便会触发UserEventTriggered事件
                            ch.pipeline().addLast(workerGroup, new CenterServerNettyHandler(config));// 业务处理
                        }
                    }).option(ChannelOption.SO_BACKLOG, 10000);// 配置TCP参数
            b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
            b.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
            //通过NoDelay禁用Nagle,使消息立即发出去，不用等待到一定的数据量才发出去
            b.option(ChannelOption.TCP_NODELAY, true);
            b.childOption(ChannelOption.SO_KEEPALIVE, false); // 不保持常连接状态
            b.option(ChannelOption.SO_TIMEOUT, 5000);
            b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
            doBind();
        } catch (Exception e) {
            LogManager.errorLog(this.getClass(),"启动配置中心失败 Server=" + config.getHostUrl(),e);
        } finally {
            // 优雅退出 释放线程池资源
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
            LogManager.infoLog(this.getClass(), "配置中心停止 Server=" + config.getHostUrl());
        }
    }

    /**
     * 启动服务监听
     */
    protected void doBind() {
        ChannelFuture f;
        try {
        	String[] tmp = config.getHostUrl()!=null?config.getHostUrl().split(":"):null;
        	String host = LANAddress.getLocalHostLANAddress().getHostAddress();
        	int port = 2000;
        	if(tmp!=null){
            	host = tmp[0];
            	if(tmp.length>1){
            		port = Integer.parseInt(tmp[1]);
            	}
        	}
            f = b.bind(host, port).sync();
            f.addListener(new ChannelFutureListener() {
                public void operationComplete(final ChannelFuture f)throws Exception {
                    if (f.isSuccess()) {
                        LogManager.infoLog(this.getClass(),"配置中心启动成功  Server=" + config.getHostUrl());
                    }else{
                    	LogManager.infoLog(this.getClass(), "配置中心启动失败 Server=" + f.cause().getMessage());
                    }
                }
            });
            f.channel().closeFuture().sync();
        } catch (Exception e) {
        	LogManager.errorLog(this.getClass(),"启动doBind失败>>> Server=" + config.getHostUrl(),e);
        }
    }

	/**
	 * 关闭配置中心
	 */
    public synchronized static void stop() {
        try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
		}
    	if(server.workerGroup!=null){
    		server.workerGroup.shutdownGracefully();
    	}
    	if(server.bossGroup!=null){
    		server.bossGroup.shutdownGracefully();
    	}
	}
    
}
